﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Diagnostics.Contracts;

using King.Extensions;
using King.Text;
using King.Reflection;
using King.Language;
using King.Language.CSharp.Antlr3;

using CSharpLexer = King.Language.CSharp.Antlr3.AntlrCSharpPreProcessorLexer;

using Antlr.Runtime;
using Antlr.Runtime.Tree;

using King.Antlr3;


namespace King.Language.CSharp {

    public enum CSharpAstNodeType {
        ROOT,

        BOOLEAN_LITERAL,
        CLOSE_PARENTHESIS,
        COMMA,
        CONDITIONAL_SYMBOL,
        DELIMITED_COMMENT,
        DOUBLE_AMPERSTAND,
        DOUBLE_BAR,
        DOUBLE_EQUAL,
        EXCLAMATION,
        EXCLAMATION_EQUAL,
        INTEGER,
        NEW_LINE,
        OPEN_PARENTHESIS,

        PP_DEFINE,
        PP_ELSE,
        PP_ELSE_IF,
        PP_END_IF,
        PP_END_REGION,
        PP_ERROR,
        PP_LINE,
        PP_LINE_DEFAULT,
        PP_LINE_HIDDEN,
        PP_IF,
        PP_REGION,
        PP_SKIPPED_CHARACTERS,
        PP_UNDEFINE,
        PP_WARNING,

        PRAGMA_WARNING_DISABLE,
        PRAGMA_WARNING_RESTORE,

        SINGLE_LINE_COMMENT,
        WHITESPACE,
    }
    public enum CSharpExpressionType {
        Not,
        OrElse,
        AndAlso,
        Equal,
        NotEqual,
        Constant,
        Variable,
    }

    public static class CSharpPreProcessorParser {

        public static TextReader PreProcess(TextReader code, IEnumerable<string> preProcessorSymbols = null) {

            // pre-process code
            var tree = ParseInternal(code, preProcessorSymbols);

            // gather active partitions
            var treeStream = new CommonTreeNodeStream(tree);
            var walker = new AntlrCSharpPreProcessorWalker(treeStream);
            typeof(CSharpPreProcessorAst).Invoke("start", walker);
            var activePartitions = walker.Tokens().ToList(o => new KeyValuePair<int, int>(o.StartIndex, o.StopIndex));

            // strip inactive text
            var preProcessedCode = new PreProcessedStream(code, activePartitions);

            return preProcessedCode;
        }
        public static TextReader PreProcess(string code, IEnumerable<string> preProcessorSymbols = null) {
            return PreProcess(new StringReader(code), preProcessorSymbols);
        }
        public static TextReader PreProcess(Stream stream, IEnumerable<string> preProcessorSymbols = null) {
            return PreProcess(new StreamReader(stream), preProcessorSymbols);
        }
        public static TextReader PreProcessFile(string file, IEnumerable<string> preProcessorSymbols = null) {
            return PreProcess(new StreamReader(file), preProcessorSymbols);
        }

        public static IEnumerable<CSharpPreProcessorAst> Parse(TextReader code, IEnumerable<string> preProcessorSymbols = null) {

            var token = ParseInternal(code, preProcessorSymbols);

            if (token.IsDefault)
                return Enumerable.Empty<CSharpPreProcessorAst>();

            if (!token.IsNil)
                return Construct(token).ToEnumerable<CSharpPreProcessorAst>();

            return token.Children().Select(o => Construct(o));
        }
        public static IEnumerable<CSharpPreProcessorAst> Parse(string code, IEnumerable<string> preProcessorSymbols = null) {
            return Parse(new StringReader(code), preProcessorSymbols);
        }
        public static IEnumerable<CSharpPreProcessorAst> Parse(Stream stream, IEnumerable<string> preProcessorSymbols = null) {
            return Parse(new StreamReader(stream), preProcessorSymbols);
        }
        public static IEnumerable<CSharpPreProcessorAst> ParseFile(string file, IEnumerable<string> preProcessorSymbols = null) {
            return Parse(new StreamReader(file), preProcessorSymbols);
        }

        private static CSharpPreProcessorAst ConstructComments(Token token) {

            //var type = tree.Token.Type;

            //// comment delimited
            //if (type == CSharpLexer.DELIMITED_COMMENT)
            //    return new CSharpComment(Regex.Match(token.Text, @"^[/][*](?<comment>.*)[*][/]$").Get("comment"), true, token);

            // comment single-line
            //if (type == CSharpLexer.SINGLE_LINE_COMMENT)
            //    return new CSharpComment(Regex.Match(token.Text, @"^[/][/](?<comment>.*)$").Get("comment"), false, token);

            return null;
        }
        private static CSharpPreProcessorAst ConstructPreProcessor(Token token, IEnumerable<CSharpPreProcessorAst> children) {

            //var type = tree.Token.Type;
            //var parentType = tree.Parent == null ? 0 : tree.Parent.Type;

            //// #define
            //if (type == CSharpLexer.PP_DEFINE)
            //    return new CSharpPreProcessorDeclaration(true, children, token);

            //// #undef
            //else if (type == CSharpLexer.PP_UNDEFINE)
            //    return new CSharpPreProcessorDeclaration(false, children, token);

            //else if (type == CSharpLexer.PP_WARNING || type == CSharpLexer.PP_ERROR) {
            //    var message = Regex.Match(token.Text, @"^#(warning|error) (?<message>.*)$").Get("message");

            //    // #warning
            //    if (type == CSharpLexer.PP_WARNING)
            //        return new CSharpPreProcessorWarning(message, token);

            //    // #error
            //    else if (type == CSharpLexer.PP_ERROR)
            //        return new CSharpPreProcessorError(message, token);
            //}

            //// #pragma warning restore
            //else if (type == CSharpLexer.PP_PRAGMA_WARNING_RESTORE)
            //    return new CSharpPreProcessorPragma(true, children, token);

            //// #pragma warning disable
            //else if (type == CSharpLexer.PP_PRAGMA_WARNING_DISABLE)
            //    return new CSharpPreProcessorPragma(false, children, token);

            //// #region
            //else if (type == CSharpLexer.PP_REGION)
            //    return new CSharpPreProcessorRegion(Regex.Match(token.Text, @"^#region\s*(?<value>.*)$").Get("value"), children, token);

            //// #line default
            //else if (type == CSharpLexer.PP_LINE_DEFAULT)
            //    return new CSharpPreProcessorLine(false, token);

            //// #line hidden
            //else if (type == CSharpLexer.PP_LINE_HIDDEN)
            //    return new CSharpPreProcessorLine(true, token);

            //// #line
            //else if (type == CSharpLexer.PP_LINE) {
            //    var line = Regex.Match(token.Text, @"^#line\s*(?<line>\d+)\s*([""](?<file>.*)[""])?$");
            //    return new CSharpPreProcessorLine(line.Get<int>("line"), line.Get("file"), token);

            //    // <conditional>
            //} else if (type == CSharpLexer.PP_CONDITIONAL)
            //    return new CSharpPreProcessorConditional(children);

            //// #if
            //else if (type == CSharpLexer.PP_IF)
            //    return new CSharpPreProcessorIf(children, token);

            //// #elif
            //else if (type == CSharpLexer.PP_ELSE_IF)
            //    return new CSharpPreProcessorElif(children, token);

            //// #else
            //else if (type == CSharpLexer.PP_ELSE)
            //    return new CSharpPreProcessorElse(children, token);

            //// if-defed-out
            //else if (type == CSharpLexer.PP_INACTIVE_SECTION)
            //    return new CSharpPreProcessor(true, token);

            //// input
            //else if (type == CSharpLexer.PP_ACTIVE_SECTION)
            //    return new CSharpPreProcessor(false, token);

            //// pre-processor symbol
            //else if (type == CSharpLexer.CONDITIONAL_SYMBOL) {

            //    // declaration
            //    if (parentType == CSharpLexer.PP_DEFINE || parentType == CSharpLexer.PP_UNDEFINE)
            //        return new CSharpSymbol(token.Text, token);
            //}

            return null;
        }
        private static CSharpPreProcessorAst ConstructExpression(Token token, IEnumerable<CSharpPreProcessorAst> children) {

            //var type = tree.Token.Type;

            //// binary operator "&&"
            //if (type == CSharpLexer.DOUBLE_AMPERSTAND)
            //    return new CSharpBinaryExpression(CSharpExpressionType.AndAlso, children, token);

            //// binary operator "||"
            //else if (type == CSharpLexer.DOUBLE_BAR)
            //    return new CSharpBinaryExpression(CSharpExpressionType.OrElse, children, token);

            //// binary operator "=="
            //else if (type == CSharpLexer.DOUBLE_EQUAL)
            //    return new CSharpBinaryExpression(CSharpExpressionType.Equal, children, token);

            //// binary operator "!="
            //else if (type == CSharpLexer.EXCLAMATION_EQUAL)
            //    return new CSharpBinaryExpression(CSharpExpressionType.NotEqual, children, token);

            //// unary operator "!"
            //else if (type == CSharpLexer.EXCLAMATION)
            //    return new CSharpUnaryExpression(CSharpExpressionType.Not, children, token);

            //// true/false
            //else if (type == CSharpLexer.BOOLEAN_LITERAL)
            //    return new CSharpConstantExpression(bool.Parse(token.Text), token);

            //// true/false
            //else if (type == CSharpLexer.INTEGER)
            //    return new CSharpConstantExpression(int.Parse(token.Text), token);

            return null;
        }
        private static CSharpPreProcessorAst Construct(Token token) {

            //var children = token.GetChildren().Select(o => Construct(o));

            //CSharpPreProcessorCode node = null;

            //// pre-processor
            //if ((node = ConstructPreProcessor(token, children)) != null)
            //    return node;

            //// comment
            //else if ((node = ConstructComments(token)) != null)
            //    return node;

            //// expression
            //else if ((node = ConstructExpression(token, children)) != null)
            //    return node;

            //else if (type == CSharpLexer.CONDITIONAL_SYMBOL)
            //    return new CSharpVariableExpression(token.Text, token);

            //else
            throw new NotImplementedException();
        }

        internal unsafe static Token ParseInternal(TextReader code, IEnumerable<string> preProcessorSymbols = null) {
            var rule = "start";

            // activate pre-processor parser
            var stream = new ANTLRReaderStream(code);
            var lexer = new AntlrCSharpPreProcessorLexer(stream);
            var tokens = new MyCommonTokenStream(lexer);
            var parser = new AntlrCSharpPreProcessorParser(tokens);

            // adapter
            int cBuffer = 256 * 256;
            byte* pBuffer = stackalloc byte[cBuffer];
            var adapter = new TokenTreeAdaptor(pBuffer, cBuffer, tokenNames: AntlrCSharpPreProcessorParser.tokenNames, stream: stream);

            // expose adapter to create tokens/trees
            parser.TreeAdaptor = adapter;
            lexer.TokenTreeAdapter = adapter;

            // set symbols
            parser.AddPreProcessorSymbols(preProcessorSymbols);

            // pre-process code
            var scope = (IAstRuleReturnScope)typeof(AntlrCSharpPreProcessorParser).Invoke(rule, parser);
            var token = (Token)scope.Tree;

            return token;
        }
    }
    public abstract class CSharpPreProcessorAst : Ast {
        protected CSharpPreProcessorAst(IEnumerable<Ast> children = null, Token? token = null)
            : base(children, token) {
        }
    }
    internal class PreProcessedStream : TextReader {

        private TextReader m_reader;
        private long m_position;
        private IEnumerator<KeyValuePair<int, int>> m_activePartitions;

        public PreProcessedStream(TextReader reader, IEnumerable<KeyValuePair<int, int>> activePartitions) {
            m_reader = reader;
            m_position = -1;
            m_activePartitions = activePartitions.GetEnumerator();
        }

        private bool IsActive() {
            if (m_activePartitions == null)
                return false;

            while (m_position == -1 || m_activePartitions.Current.Value >= m_position) {

                if (!m_activePartitions.MoveNext()) {
                    m_activePartitions = null;
                    return false;
                }
            }

            return m_activePartitions.Current.Key <= m_position;
        }

        public override void Close() {
            m_reader.Close();
        }
        protected override void Dispose(bool disposing) {
            if (disposing)
                GC.SuppressFinalize(this);

            m_reader.Dispose();
        }

        public override int Peek() {
            var result = m_reader.Peek();

            if (result != -1 && !IsActive())
                result = (int)' ';

            return result;
        }
        public override int Read() {
            var result = Peek();

            m_position++;
            m_reader.Read();

            return result;
        }
    }
    
    public sealed class CSharpPreProcessorDeclaration : CSharpPreProcessorAst {

        private bool m_define;

        public CSharpPreProcessorDeclaration(bool define, IEnumerable<Ast> children, Token? token = null) 
            : base(children, token) {
            m_define = define;
        }

        public bool IsDefine {
            get { return m_define; }
        }
        public string Symbol {
            get { return ((CSharpSymbol)Children().Single()).Symbol; }
        }
    }

    public sealed class CSharpPreProcessorConditional : CSharpPreProcessorAst {

        public CSharpPreProcessorConditional(IEnumerable<CSharpAst> children = null)
            : base(children) {
        }

        public CSharpPreProcessorIf If {
            get { return (CSharpPreProcessorIf)Children().First(); }
        }
        public IEnumerable<CSharpPreProcessorElif> Elifs() {
            return Children().Skip(1).TakeWhile(o => o is CSharpPreProcessorElif).Cast<CSharpPreProcessorElif>();
        }
        public CSharpPreProcessorElse Else {
            get { return Children().Last() as CSharpPreProcessorElse; }
        }
    }
    public sealed class CSharpPreProcessorIf : CSharpPreProcessorAst {

        public CSharpPreProcessorIf(IEnumerable<CSharpAst> children = null, Token? token = null)
            : base(children, token) {
        }

        public CSharpExpression Test {
            get { return (CSharpExpression)Children().First(); }
        }
    }
    public sealed class CSharpPreProcessorElif : CSharpPreProcessorAst {

        public CSharpPreProcessorElif(IEnumerable<CSharpAst> children = null, Token? token = null)
            : base(children, token) {
        }

        public CSharpExpression Test {
            get { return (CSharpExpression)Children().First(); }
        }
    }
    public sealed class CSharpPreProcessorElse : CSharpPreProcessorAst {

        public CSharpPreProcessorElse(IEnumerable<CSharpAst> children = null, Token? token = null)
            : base(children, token) {
        }
    }

    public sealed class CSharpPreProcessor : CSharpPreProcessorAst {

        private bool m_skipped;

        public CSharpPreProcessor(bool skipped = false, Token? token = null)
            : base(token: token) {

            m_skipped = false;
        }

        public bool Skipped {
            get { return m_skipped; }
        }
        public string Text {
            get { return Token.Text; }
        }
    }
    public sealed class CSharpPreProcessorPragma : CSharpPreProcessorAst {

        private bool m_restore;

        public CSharpPreProcessorPragma(bool restore, IEnumerable<CSharpAst> children = null, Token? token = null)
            : base(children, token) {

            m_restore = restore;
        }

        public IEnumerable<int> Warnings() {
            return Children().Cast<CSharpConstantExpression>().Select(o => (int)o.Value);
        }
        public bool Restore {
            get { return m_restore; }
        }
    }
    public sealed class CSharpPreProcessorWarning : CSharpPreProcessorAst {

        private string m_message;

        public CSharpPreProcessorWarning(string message, Token? token = null)
            : base(token: token) {

            m_message = message;
        }

        public string Message {
            get { return m_message; }
        }
    }
    public sealed class CSharpPreProcessorError : CSharpPreProcessorAst {

        private string m_message;

        public CSharpPreProcessorError(string message, Token? token = null)
            : base(token: token) {

            m_message = message;
        }

        public string Message {
            get { return m_message; }
        }
    }
    public sealed class CSharpPreProcessorRegion : CSharpPreProcessorAst {

        private string m_value;

        public CSharpPreProcessorRegion(string value, IEnumerable<CSharpAst> children = null, Token? token = null)
            : base(children, token) {

            m_value = value;
        }

        public string Value {
            get { return m_value; }
        }
    }
    public sealed class CSharpPreProcessorLine : CSharpPreProcessorAst {

        private int m_line;
        private string m_fileName;
        private bool? m_hidden;

        public CSharpPreProcessorLine(bool hidden, Token? token = null)
            : base(token: token) {
            m_hidden = hidden;
            m_line = -1;
        }
        public CSharpPreProcessorLine(int line, string fileName, Token? token = null)
            : base(token: token) {

            m_line = line;
            m_fileName = fileName;
        }

        public bool Hidden {
            get { return m_hidden == true; }
        }
        public bool Default {
            get { return m_hidden == false; }
        }
        public string FileName {
            get { return m_fileName; }
        }
        public int NewLine {
            get { return m_line; }
        }
    }

    public sealed class CSharpSymbol : CSharpPreProcessorAst {

        private string m_symbol;

        public CSharpSymbol(string symbol, Token? token = null)
            : base(token: token) {
            m_symbol = symbol;
        }

        public string Symbol {
            get { return m_symbol; }
        }
    }
    public sealed class CSharpComment : CSharpPreProcessorAst {

        private string m_comment;
        private bool? m_delimited;

        public CSharpComment(string comment, bool? delimited = null, Token? token = null)
            : base(token: token) {
            m_comment = comment;
            m_delimited = delimited;
        }

        public string Value {
            get { return m_comment; }
        }
        public bool? Delimited {
            get { return m_delimited; }
        }
    }

    public abstract class CSharpExpression : CSharpPreProcessorAst {

        private CSharpExpressionType m_expressionType;

        protected CSharpExpression(CSharpExpressionType expressionType, IEnumerable<CSharpAst> children = null, Token? token = null)
            : base(children, token) {

            m_expressionType = expressionType;
        }

        public CSharpExpressionType ExpressionType {
            get { return m_expressionType; }
        }
    }
    public sealed class CSharpVariableExpression : CSharpExpression {

        private string m_variable;

        public CSharpVariableExpression(string variable, Token? token = null)
            : base(CSharpExpressionType.Variable, token: token) {

                m_variable = variable;
        }

        public string Symbol {
            get { return m_variable; }
        }
    }
    public sealed class CSharpConstantExpression : CSharpExpression {

        private object m_value;

        public CSharpConstantExpression(object value, Token? token = null)
            : base(CSharpExpressionType.Constant, token: token) {

            m_value = value;
        }

        public object Value {
            get { return m_value; }
        }
    }
    public sealed class CSharpBinaryExpression : CSharpExpression {

        public CSharpBinaryExpression(
            CSharpExpressionType expressionType, IEnumerable<CSharpAst> children, Token? token = null)
            : base(expressionType, children, token) {
        }

        public CSharpExpression Left {
            get { return (CSharpExpression)Children().First(); }
        }
        public CSharpExpression Right {
            get { return (CSharpExpression)Children().Second(); }
        }

    }
    public sealed class CSharpUnaryExpression : CSharpExpression {

        public CSharpUnaryExpression(
            CSharpExpressionType expressionType, IEnumerable<CSharpAst> children, Token? token = null)
            : base(expressionType, children, token) {
        }

        public CSharpExpression Operand {
            get { return (CSharpExpression)Children().Single(); }
        }
    }
}
